package com.pf.common.helper;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.pf.dto.BaseResult;
import com.pf.enums.BaseResultEnum;
import com.pf.exception.RetryException;
import com.pf.mapper.entity.JobInfo;
import com.pf.utils.DateUtils;
import com.pf.utils.JacksonUtils;
import com.pf.utils.LogUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * @author PanFei
 * @version 1.0.0
 * @createTime 2021/8/10
 */
@Slf4j
@Component
public class ScheduledJobHelper {

    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RestTemplate restTemplate;
    //    @Autowired
//    private LogInfoService logInfoService;
    @Autowired
    private ScheduledJobRetryHelper scheduledJobRetryHelper;
    @Autowired
    private AlarmMailHelper alarmMailHelper;


    /**
     * job map
     */
    private static final ConcurrentHashMap<String, ScheduledFuture> enabledScheduledFutureMap = new ConcurrentHashMap<>();

    /**
     * start job
     *
     * @param jobInfo
     */
    public void start(JobInfo jobInfo) {
        Assert.notNull(jobInfo, "任务信息不能为空！");
        log.info(">>>>>ScheduledJobHelper start : job size=[{}]", enabledScheduledFutureMap.size());
        ScheduledFuture scheduledFuture = enabledScheduledFutureMap.get(jobInfo.getJobId());
        if (scheduledFuture == null) {
            scheduledFuture = threadPoolTaskScheduler.schedule(new ScheduledTask(jobInfo), new CronTrigger(jobInfo.getJobCron()));
            enabledScheduledFutureMap.put(jobInfo.getJobId(), scheduledFuture);
        } else {
            log.info(">>>>> start 任务已存在！jobId={}", jobInfo.getJobId());
        }
        log.info(">>>>> 启动后： ScheduledJobHelper job size=[{}]", enabledScheduledFutureMap.size());
    }

    /**
     * 停用调度任务
     *
     * @param jobId
     */
    public void stop(String jobId) {
        log.info(">>>>> 停用前： ScheduledJobHelper job size=[{}]", enabledScheduledFutureMap.size());
        ScheduledFuture scheduledFuture = enabledScheduledFutureMap.get(jobId);
        if (scheduledFuture != null) {
            // 取消任务
            // 参数为true，表示可以中断执行中的线程。interruption
            // 参数为false，只能取消还没有开始的任务，若任务已经开始了，就任由其运行下去
            scheduledFuture.cancel(true);
            if (scheduledFuture.isCancelled()) {
                enabledScheduledFutureMap.remove(jobId);
            }
        }
        log.info(">>>>> 停用后： ScheduledJobHelper job size=[{}]", enabledScheduledFutureMap.size());
    }


    /**
     * 执行调度任务
     */
    private class ScheduledTask implements Runnable {
        private JobInfo jobInfo;

        public ScheduledTask(JobInfo jobInfo) {
            this.jobInfo = jobInfo;
        }

        @Override
        public void run() {
            // 获取redis锁
            final String lockKey = "REDISSON_JOB_" + jobInfo.getJobId();
            RLock lock = redissonClient.getLock(lockKey);
            // 加锁
            lock.lock();

            String url = jobInfo.getJobUrl();
            try {

                // 远程调用目标任务
                scheduledJobRetryHelper.retryExecuteJob(jobInfo, () -> {
                    ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
                    if (HttpStatus.OK.equals(responseEntity.getStatusCode())) {
                        BaseResult baseResult = JacksonUtils.readValue(responseEntity.getBody(), BaseResult.class);
                        if (baseResult != null && baseResult.getSuccess()) {
                            log.info(">>>>> 定时任务接口：{} -> 调用成功！", url);
                        } else {
                            log.error(">>>>> 定时任务接口：{} -> 调用失败！", url);
                            log.error(">>>>> 报错堆栈信息：{}", baseResult.getStackTraceMsg());

                            // 发送报警邮件
                            List<String> toUsers = Lists.newArrayList(StringUtils.split(jobInfo.getAlarmMail()));
                            Map<String, Object> data = ImmutableMap.of("jobName", jobInfo.getJobName(), "jobUrl", jobInfo.getJobUrl(),
                                    "jobCron", jobInfo.getJobCron(), "executeTime", DateUtils.getSysTime(),
                                    "stackTrace", baseResult.getStackTraceMsg());
                            String subject = jobInfo.getJobName() + "报警邮件";
                            alarmMailHelper.sendAlarmMail(toUsers, null, subject, data);

                            // 任务重试 （code = 10002 表示任务需重试）
                            if (BaseResultEnum.RETRY.getCode().equals(baseResult.getCode())) {
                                log.error(">>>>> 接口：{} -> 开始重试！", url);
                                throw new RetryException(BaseResultEnum.RETRY);
                            }
                        }
                    } else {
                        log.error(">>>>> 接口：{} -> 链接失败！", url);
                        log.error(">>>>> try - 发送报警邮件....");
                    }
                    return null;
                }, RetryException.class, ResponseEntity.class);
            } catch (Exception e) {
                log.error(">>>>> 接口：{} 调用失败！", url);
                log.error(">>>>> catch - 发送报警邮件....");
                log.error(">>>>> errorCause:{}", LogUtils.getStackTrace(e));
            } finally {
                // 解锁
                lock.unlock();
                log.info(">>>>> 释放锁成功！jobId={}", jobInfo.getJobId());
            }
        }
    }
}
